﻿using System;
using System.Linq.Expressions;
using System.Reflection;

namespace com.ddmresources.EnumMetadata.Core
{
    /// <summary>
    /// This class provides methods that are useful in converting from method expressions to standard Reflection PropertyInfo types.
    /// These types give infomation (such as the method name) about methods thast allows consumers to avoid hard coding strings into their app
    /// when they need to reference method names.
    /// </summary>
    public static class ReflectionUtil
    {
        /// <summary>
        /// This method will return the PropertyInfo object that is associated with the passed in Property (specified by a lambda expression) of the type T. 
        /// The PropertyInfo object contains details such as the property's name, which allows consumers to use property names without hardcoding them as strings.
        /// 
        /// This method will throw an ArgumentException if the passed in expression resolves to a method type other than property.
        /// </summary>
        /// <typeparam name="T">The type whose method is being reflected upon</typeparam>
        /// <param name="expression">A lambda expression which resolves to a property on the specified type T</param>
        /// <returns>The PropertyInfo object associated with the property of the type that was indicated by the expression argument</returns>
        public static PropertyInfo GetProperty<T>(Expression<Func<T>> expression)
        {
            var property = GetMemberInfo(expression) as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException("Not a property expression", expression.Name);
            }

            return property;
        }

        /// <summary>
        /// This method will return the MemberInfo object that is associated with the passed in member (specified by a lambda expression) of the type T. 
        /// The MemmberInfo object contains details such as the member's name, which allows consumers to use member names without hardcoding them as strings.
        /// 
        /// This method will throw an ArgumentException if the passed in expression resolves to something other than a method.
        /// </summary>
        /// <param name="lambda">A lambda expression which resolves to a property on the involved type</param>
        /// <returns>The MemberInfo object associated with the member of the type that was indicated by the expression argument</returns>
        private static MemberInfo GetMemberInfo(LambdaExpression lambda)
        {
            if (lambda == null)
            {
                throw new ArgumentException("Not a member expression");
            }

            MemberExpression memberExpression = null;
            if (lambda.Body.NodeType == ExpressionType.Convert)
            {
                memberExpression = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
            }
            else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
            {
                memberExpression = lambda.Body as MemberExpression;
            }
            else if (lambda.Body.NodeType == ExpressionType.Call)
            {
                return ((MethodCallExpression)lambda.Body).Method;
            }

            if (memberExpression == null)
            {
                throw new ArgumentException("Not a member access");
            }

            return memberExpression.Member;
        }
    }
}
